#!/usr/bin/env python

# Copyright (c) 2009, Purdue University
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
# 
# Neither the name of the Purdue University nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Config check tool for Roster"""


__copyright__ = 'Copyright (C) 2009, Purdue University'
__license__ = 'BSD'
__version__ = '0.17'


import sys
import os
import tarfile
import shutil
import ConfigParser
import glob
import iscpy
from fabric import api as fabric_api
from fabric import state as fabric_state

from optparse import OptionParser


def ChangeNamedDirectory(file_name, directory):
  """Changed the directory for named in options.

  Inputs:
    file_name: string of the filename
    directory: string of the directory
  """
  file_handle = open(file_name, 'r')
  try:
    file_contents = file_handle.readlines()
    file_handle.close()
    named_dict = iscpy.ParseISCString(''.join(file_contents))
    named_dict['options']['directory'] = "\"%s\"" % directory
    file_handle = open(file_name, 'w')
    file_handle.writelines(iscpy.MakeISC(named_dict))
  finally:
    file_handle.close()


def main(args):
  """Collects command line arguments. Checks configs.

  Inputs:
    args: list of arguments from the command line
  """
  usage = ('\n'
           '\n'
           'To check config files in an audit id:\n'
           '\t%s -i <audit-id> [--config-file <config-file>]\n'
           '\t[-z <checkzone-binary>] [-c <checkconf-binary>] [-v]\n' % sys.argv[0])

  parser = OptionParser(version='%%prog (Roster %s)' % __version__, usage=usage)

  parser.add_option('-d', '--directory', action='store', dest='directory',
                    help='Directory to scan.', metavar='<directory>',
                    default=None)
  parser.add_option('-o', '--output-directory', action='store',
                    dest='output_directory',
                    help='Directory to temporarily output files to. Must be '
                         'same in named.conf.', default='temp_dir')
  parser.add_option('-z', '--named-checkzone', action='store',
                    dest='named_checkzone',
                    help='Location of named_checkzone binary.',
                    default='/usr/sbin/named-checkzone')
  parser.add_option('-c', '--named-checkconf', action='store',
                    dest='named_checkconf',
                    help='Location of named_checkconf binary.',
                    default='/usr/sbin/named-checkconf')
  parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                    help='Make command verbose.', default=False)
  parser.add_option('--config-file', action='store', dest='config_file',
                    help='Config File Location', metavar='<config-file>',
                    default='/etc/roster/roster_server.conf')
  parser.add_option('-i', '--id', dest='id',
                    help='ID of tarfile output from Roster tree export.',
                    metavar='<id>', default=None)

  (globals()['options'], args) = parser.parse_args(args)


  if( os.path.exists(options.output_directory) ):
    shutil.rmtree(options.output_directory)

  server_config_file = ConfigParser.SafeConfigParser()
  server_config_file.read(options.config_file)

  backup_directory = options.directory
  if( not options.directory ):
    backup_directory = server_config_file.get('exporter',
                                              'backup_dir').rstrip('/')

  temp_root_directory = server_config_file.get('exporter',
                                               'root_config_dir').rstrip('/')
  if( not os.path.exists(temp_root_directory) ):
    os.makedirs(temp_root_directory)

  err_files = []
  try:
    file_list = os.listdir(backup_directory)
  except OSError:
    print ('ERROR: Directory %s does not exist or you '
           'do not have permission.' % backup_directory)
    sys.exit(1)
  if( options.id ):
    for file_name in file_list:
      if( not file_name.startswith('dns_tree') ):
        continue
      try:
        if( file_name.split('-')[1].split('.')[0] == options.id ):
          tar = tarfile.open('%s/%s' % (backup_directory, file_name))
          try:
            tar.extractall()
          finally:
            tar.close()
          shutil.move(temp_root_directory, options.output_directory)
          break
      except IndexError:
        err_files.append(file_name)
        continue
        # If this error occurs, skip if correct numbered file is found.
    else:
      # No break, could mean wrong filenames or nonexistant
      if( err_files ):
        print 'ERROR: Filenames: %s not named correctly' % ', '.join(err_files)
      else:
        print 'ERROR: ID %s not found.' % options.id
      sys.exit(1)
  else:
    newest = -1
    new_file_name = None
    for file_name in file_list:
      if( not file_name.startswith('dns_tree') ):
        continue
      try:
        id = int(file_name.split('-')[1].split('.')[0])
      except IndexError:
        err_files.append(file_name)
        break
        # If this error occurs, error out completely
      if( id > newest ):
        newest = id
        new_file_name = file_name
    file_name = new_file_name
    if( err_files ):
      print 'ERROR: Filenames: %s not named correctly' % ', '.join(err_files)
      sys.exit(1)
    if( new_file_name is None ):
      print ('ERROR: An unknown error has occurred trying to find the newest '
             'file.')
      sys.exit(1)
    tar = tarfile.open('%s/%s' % (backup_directory, file_name))
    try:
      tar.extractall()
    finally:
      tar.close()
    shutil.move(temp_root_directory, options.output_directory)

  if( not os.path.exists(options.named_checkzone) ):
    print 'ERROR: Could not find "named-checkzone" binary.'
    sys.exit(1)
  if( not os.path.exists(options.named_checkconf) ):
    print 'ERROR: Could not find "named-checkconf" binary.'
    sys.exit(1)

  fabric_state.output['running'] = False
  fabric_state.output['warnings'] = False
  fabric_api.env.warn_only = True
  named_files = glob.glob('%s/*/named.conf' % options.output_directory)
  for current_file in named_files:
    ChangeNamedDirectory(current_file, options.output_directory)
    checknamed_text = fabric_api.local('%s %s' % (
        options.named_checkconf, current_file), capture=True)
    if( checknamed_text == '' ):
      if( options.verbose ):
        print 'Finished - %s' % current_file
    else:
      print 'ERROR: %s' % checknamed_text
      sys.exit(1)


  zone_files = glob.glob('%s/*/*/*/*.db' % options.output_directory)
  for current_file in zone_files:
    file_handle = open(current_file, 'r')
    try:
      file_contents = file_handle.read().split('\n')
    finally:
      file_handle.close()
    for line in file_contents:
      if( line.startswith('$ORIGIN') and len(line.split()) == 2 ):
        origin = line.split()[1]
        break
    else:
      print 'ERROR: Could not find $ORIGIN in "%s"' % current_file
      sys.exit(1)
    checkzone_text = fabric_api.local('%s %s %s' % (
        options.named_checkzone, origin,
                                    current_file), capture=True)
    if( checkzone_text.split('\n')[1] == 'OK' ):
      if( options.verbose ):
        print 'Finished - %s' % current_file
    else:
      print 'ERROR: %s' % checkzone_text
      sys.exit(1)

  if( options.verbose ):
    print '--------------------------------------------------------------------'
    print 'Checked %s named.conf file(s) and %s zone file(s)\n' % (
        len(named_files), len(zone_files))
    print 'All checks successful'

if __name__ == '__main__':
    main(sys.argv[1:])
